#include "usb.h"
#include "sys/types.h"
#include "iodev.h"
#include "iodev.h"
#include "ioctl.h"
#include "syscfg.h"

//#define cdc_cpu_relax()		cpu_relax()

int cdc_running = 0;

static const uint8_t device_desc[] = {
        USB_DEVICE_DESC_SIZE, /* bLength */
        USB_DEVICE_DESCRIPTOR_TYPE, /* bDescriptorType */
        0x10, 0x01, /* bcdUSB, 0110 */
        USB_CLASS_COMM, /* bDeviceClass */
        0x00, /* bDeviceSubClass */
        0x00, /* bDeviceProtocol */
        MAX_PACKAGE_SIZE,
        0x2d, 0x26,/* Vendor ID 0x262d */
        0x08, 0x02,/* Product ID 0x0208*/
        0x00, 0x01,/* BCD Device version 0100*/
        0x01, /* iManufacturer */
        0x02, /* iProduct */
        0x03, /* iSerialNumber */
        0x01  /* bNumConfigurations */
} ;

#define TOTAL_LENGTH0	67
#define TOTAL_LENGTH	( USB_CONFIGURATION_DESC_SIZE 		\
						+ USB_INTERFACE_DESC_SIZE			\
						+ HEADER_FUNC_DEC_LENGTH 			\
						+ CALL_MANAGEMENT_FUNC_DESC_LENGTH	\
						+ ACM_FUNC_DESC_LENGTH				\
						+ UNION_FUNC_DESC_LENGTH			\
						+ USB_ENDPOINT_DESC_SIZE			\
						+ USB_INTERFACE_DESC_SIZE			\
						+ USB_ENDPOINT_DESC_SIZE			\
						+ USB_ENDPOINT_DESC_SIZE			\
						)
static const uint8_t config_desc[] = {
        /*************** Configuration Descriptor ***********************/
        /* Configuration 1 */
        USB_CONFIGURATION_DESC_SIZE, /* bLength */
        USB_CONFIGURATION_DESCRIPTOR_TYPE, /* bDescriptorType */
        TOTAL_LENGTH & 0xFF, /* wTotalLength Low Byte */
        (TOTAL_LENGTH >> 8) & 0xFF, /* wTotalLength High Byte*/
        0x02, /* bNumInterfaces */
        0x01, /* bConfigurationValue */
        0x00, /* iConfiguration */
        0x80,//0xc0,// /* bmAttributes: 0x80 for Bus Powered, 0xC0 for self powered */
        0x96,//0x01,// /* bMaxPower: MaxPower 300 mA,this current is used for target */

        /*************** CDC Class Interface Descriptor ****************/
        /* Interface 0, Alternate Setting 0, Class Code Unknown */
        USB_INTERFACE_DESC_SIZE, /* bLength */
        USB_INTERFACE_DESCRIPTRO_TYPE,  /* bDescriptorType */
        0x00, /* bInterfaceNumber */
        0x00, /* bAlternateSetting */
        0x01, /* bNumEndpoints */
        USB_CLASS_COMM, /* bInterfaceClass, Communication Interface Class */
        USB_CDC_SUBCLASS_ACM, /* bInterfaceSubClass, Abstract Control Model */
        0x01, /* bInterfaceProtocol, No Class Specific Protocol */
        0x00, /* iInterface */

        /********* Header Functional Descriptor ********/
        HEADER_FUNC_DEC_LENGTH, /* bFunctionLength */
        DESC_TYPE_CS_INTERFACE, /* bDescriptorType */
        DESC_SUBTYPE_CS_HEADER_FUNC, /* bDescriptorSubType */
        0x10, /* bcdCDC 0110*/
        0x01,

        /**** Call Management Functional Descriptor ****/
        CALL_MANAGEMENT_FUNC_DESC_LENGTH, /* bFunctionLength */
        DESC_TYPE_CS_INTERFACE, /* bDescriptorType */
        DESC_SUBTYPE_CS_CALL_MAN,  /* bDescriptorSubType */
        0x00, /* bmCapabilities (only over Communication Class IF / handles itself) */
        0x00, /* bDataInterface (Interface number of Data Class interface) */

        /*** Abstract Control Management Functional Descriptor ***/
        ACM_FUNC_DESC_LENGTH, /* bFunctionLength */
        DESC_TYPE_CS_INTERFACE, /* bDescriptorType (CS_INTERFACE) */
        DESC_SUBTYPE_CS_ABST_CNTRL, /* bDescriptorSubtype (Abstract Control Management) */
        0x02, /* bmCapabilities (Device supports the requests SET_LINE_CODING
                                       SET_CONTROL_LINE_STATE,
                                       GET_LINE_CODING) */

        /***  Union Functional Descriptor  **/
        UNION_FUNC_DESC_LENGTH, /* bFunctionLength */
        DESC_TYPE_CS_INTERFACE, /* bDescriptorType (CS_INTERFACE) */
        DESC_SUBTYPE_CS_UNION_FUNC, /* bDescriptorSubtype (Union Functional) */
        0x00,   /* bMasterInterface (Master Interface) */
        0x01,   /* bSlaveInterface  (Slave Interface) */

        /***********  EP Interrupt Descriptor  *******/
        USB_ENDPOINT_DESC_SIZE, /* bLength */
        USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType */
        CDC_EP_INT_ADDR, /* bEndpointAddress */
        USB_ENDPOINT_TYPE_INTERRUPT, /* bmAttributes */
        0x40, /* wMaxPacketSize low byte*/
        0x00, /* wMaxPacketSize high byte*/
        0x0A, /* 10 ms */

        /* Data Interface class (1) */
        /* Interface 1, Alternate Setting 1, Class Code Unknown */
        USB_INTERFACE_DESC_SIZE,  /* bLength */
        USB_INTERFACE_DESCRIPTRO_TYPE, /* bDescriptorType */
        0x01, /* bInterfaceNumber */
        0x00, /* bAlternateSetting */
        NUM_ENDPOINTS,  /* bNumEndpoints */
        USB_CLASS_DATA, /* bInterfaceClass, Communication Interface Class */
        0x00, /* bInterfaceSubClass, Abstract Control Model */
        0x00, /* bInterfaceProtocol, USB_PROTOCOL_CODE_UNKNOWN */
        0x00, /* iInterface */

        /* Endpoint, EP Bulk In */
        USB_ENDPOINT_DESC_SIZE, /* bLength */
        USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType */
        CDC_EP_IN_ADDR, /* bEndpointAddress */
        USB_ENDPOINT_TYPE_BULK, /* bmAttributes */
        0x40, /* wMaxPacketSize low byte*/
        0x00, /* wMaxPacketSize high byte*/
        0x00,

        /* Endpoint, EP Bulk Out */
        USB_ENDPOINT_DESC_SIZE, /* bLength */
        USB_ENDPOINT_DESCRIPTOR_TYPE, /* bDescriptorType */
        CDC_EP_OUT_ADDR, /* bEndpointAddress */
        USB_ENDPOINT_TYPE_BULK, /* bmAttributes */
        0x40, /* wMaxPacketSize low byte*/
        0x00, /* wMaxPacketSize high byte*/
        0x00
};

static const uint8_t str0_desc[] = { /* Index 0x00: LANGID Codes */
        0x04, /* bLength */
        USB_STRING_DESCRIPTOR_TYPE, /* bDescriptorType */
        0x09, /* US English 0409 */ /* wLANGID */
        0x04  /* US English 0409 */ /* wLANGID */
};

static const uint8_t str1_desc[] = {
        0x04,  /* bLength */
        USB_STRING_DESCRIPTOR_TYPE,
        ' ',
        0x00
};

static const uint8_t str2_desc[] = {
        0x16,  /* bLength */
        USB_STRING_DESCRIPTOR_TYPE,
        'W', 0x00, 'i', 0x00, 't', 0x00, 's', 0x00,
        'i', 0x00, '.', 0x00, 'T', 0x00, '1', 0x00,
       // '0', 0x00, '2', 0x00, 'x', 0x00, '8', 0x00,
        '.', 0x00, 'V', 0x00, 'C', 0x00, 'O', 0x00,
        'M', 0x00
};

static const uint8_t str3_desc[] = {
        0x0E, /* bLength */
        USB_STRING_DESCRIPTOR_TYPE,
        'T', 0x00, '1', 0x00,  '2', 0x00, '0', 0x00,
        '1', 0x00, '3', 0x00
};

const usb_setup_t usb_setup = {
        .dev_desc            = device_desc,
        .cfg_desc            = config_desc,
        .str_desc            = {str0_desc, str1_desc, str2_desc, str3_desc},
        .cfg_desc_len        = sizeof (config_desc),
        .dev_desc_len        = sizeof (device_desc),
        .str_desc_len        = {
                                sizeof (str0_desc), sizeof (str1_desc),
                                sizeof (str2_desc), sizeof (str3_desc),
                               },
};

static cdc_acm_linecode_t linecode = {
                115200, 0, 0, 8
};

void class_get_line_coding(void)
{
	udc->EP_CTRL[0] = 0x00;
	cdc_running = 1;
	write_ep_data(0, (char*)&linecode, LINECODE_SIZE);
}

void class_set_line_coding( void)
{
	cdc_acm_linecode_t lc;
	if(read_ep_data(0, (char*)&lc, LINECODE_SIZE) == LINECODE_SIZE) {
		// FIXME: check validate
		// save parameter
		linecode.dte_rate = lc.dte_rate;
		// reserver the other config for [8,n,1],don't change

		cdc_running = 1;
	}
	write_ep_data(0, NULL, 0);
}

void class_request(uint8_t bRequest)
{
        switch(bRequest) {
                case ACM_REQUEST_SET_LINE_CODING:
                {
                        class_set_line_coding( );
                        break;
                }
                case ACM_REQUEST_GET_LINE_CODING:
                {
                        class_get_line_coding();
                        break;
                }
                case ACM_REQUEST_SET_CONTROL_LINE_STATE:
                {
                        write_ep_data(0, NULL, 0);
                        break;
                }
                default:
                	break;
        }
}

char * cdc_req_descriptor(device_req_t *preq)
{
	char *p = NULL;
	int len = 0;
	switch((preq->wValue >> 8) & 0xFF) {
		case USB_DEVICE_DESCRIPTOR_TYPE :
		{
			len = usb_setup.dev_desc_len;
			p = (char*)usb_setup.dev_desc;
			break ;
		}
		case USB_CONFIGURATION_DESCRIPTOR_TYPE :
		{
			len = usb_setup.cfg_desc_len;
			p = (char*)usb_setup.cfg_desc;
			break ;
		}
		case USB_STRING_DESCRIPTOR_TYPE :
		{
			unsigned char temp = preq->wValue & 0xFF;
			if(temp < 4){
				len = usb_setup.str_desc_len[temp];
				p = (char*)usb_setup.str_desc[temp];
			}
			break ;
		}
		default :
			break ;
	}
	if(preq->wLength > len)
		preq->wLength = len;
	return p;
}

/*
 * **************************************************************
 */
#if 1
#include "circbuf.h"

#define RX_BUF_SIZE		(1024*2)
#define TX_BUF_SIZE		(1024)

DECLARE_STATIC_CIRC_BUF(g_prx_cb, RX_BUF_SIZE);
#ifdef EP_IN_INT_SUPPORT
DECLARE_STATIC_CIRC_BUF(g_ptx_cb, RX_BUF_SIZE);
static int tx_ok = 1;

#endif

void cdc_reset(void)
{
#ifdef EP_IN_INT_SUPPORT
	tx_ok = 1;
#endif
	cdc_running = 0;
	CIRC_CLEAR(&g_prx_cb);
#ifdef EP_IN_INT_SUPPORT
	CIRC_CLEAR(&g_ptx_cb);
#endif
}

void cdc_out_completed(char *outbuf, int len)
{
	circbuf_t *pcb = &g_prx_cb;
	int wr = circ_buf_put(pcb, outbuf, len);
	if(wr < len) {
		// fifo full
	}
}

#ifdef EP_IN_INT_SUPPORT
int cdc_in_completed(char *inbuf, int len)
{
	circbuf_t *pcb = &g_ptx_cb;
	if(CIRC_EMPTY(pcb)) {
		tx_ok = 1;
		return 0;
	}
	return circ_buf_get(pcb, inbuf, len);
}
#endif

int cdc_write(const char *data, int len)
{
	int wr;
#ifdef EP_IN_INT_SUPPORT
	circbuf_t *pcb = &g_ptx_cb;

	if (!cdc_running) return 0;
	wr = circ_buf_put(pcb, data, len);
	if(tx_ok) {
		int cnt = CIRC_CNT(pcb);
		char buf[MAX_PACKAGE_SIZE];
		
		tx_ok = 0;
		cnt = circ_buf_get(pcb, buf, MAX_PACKAGE_SIZE);
		write_ep_data(CDC_EP_IN_NUM, buf, cnt);
	}
#else
	wr = 0;
	while(len>=0 && cdc_running) {
		int cnt = len > MAX_PACKAGE_SIZE ? MAX_PACKAGE_SIZE : len;
		write_ep_data(CDC_EP_IN_NUM, data + wr, cnt);
		wait_ep_in_complete(CDC_EP_IN_NUM);
		if(len == 0)
			break;
		wr += cnt;
		len -= MAX_PACKAGE_SIZE;
	}
#endif
	return wr;
}


int cdc_read(char *buf, int size)
{
	
	int count = CIRC_CNT(&g_prx_cb);
	
        if (size > count) size = count;
	
	disable_udc_interrupt();
	circ_buf_get(&g_prx_cb, buf, size);
	enable_udc_intterrupt();

	return size;
}


/*
 * **************************************************************
 */
#endif

static ssize_t usb_cdc_write(iodev_t *dev, const void* buffer, size_t size)
{
	return cdc_write(buffer, size);
}

static ssize_t usb_cdc_read(iodev_t *dev, void* buffer, size_t size)
{
	return cdc_read(buffer, size);
}

static int usb_cdc_poll(iodev_t *dev, uint32_t flags)
{
        if (flags & POLL_READ) {
                if (CIRC_EMPTY(&g_prx_cb))
                        flags &= ~POLL_READ;
        }

        return flags;
}


#define KHZ (1000)
#define MHZ (1000 * KHZ)
static int usb_cdc_ioctl(iodev_t *dev, uint32_t cmd, void *arg)
{
        uint32_t freq = 0;
        sys_cpufreq_notify_transition(NULL, &freq);
        
        switch (cmd) {
            case IOCTL_USB_CONNECT:
                {
                    if(freq <= 24*MHZ){
			            syscfg_pll_powerup();
		            }
                     open_USB_clk();
                     t1_udc_init();
                }
                break;
            case IOCTL_USB_DISCONNECT:
                {
                    t1_udc_disconnect(); 
                    close_USB_clk();                    
                    if(freq <= 24*MHZ){
                        syscfg_pll_powerdown();
                    }
                }
                break;
            default: 
                return -1;    
        }
        
        return 0;
}

static int usb_cdc_init(iodev_t *dev)
{
	return t1_udc_init();
}


static const iodev_t _t1_usbcdc_ __iodev_entry = {
        .name = "usbcdc",
        .priv = NULL,
#ifdef CONFIG_USB_PRINTF        
   	    .init =  usb_cdc_init,
#else
        .init =  NULL,
#endif   
        .read = usb_cdc_read,
        .write = usb_cdc_write,
        .ioctl = usb_cdc_ioctl,
        .poll = usb_cdc_poll,
};







